//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Resource Application Block
//===============================================================================

using System;
using System.Diagnostics;
using System.Resources;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;
using System.Transactions;
using Microsoft.Practices.EnterpriseLibrary.Data;

namespace EntLibContrib.Resource.Managers
{
	/// <summary>
	/// Custom resource writer for saving resources away to a database
	/// </summary>
	public class DataResourceWriter : IResourceWriter
	{
		#region Fields
		// The database to write resources away to.
		private Database database;
		// The resource base name.
		private string baseName;
		// The current culture name.
		private string cultureName;
		// The internal array of resources waiting to be written to the database.
		private SortedList resources;
		// Track whether Dispose has been called.
		private bool disposed;
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new instance of the <see cref="T:DataResourceWriter"/> class.
		/// </summary>
		/// <param name="database">The database.</param>
		/// <param name="baseName">the base name for the set of resources.</param>
		/// <param name="cultureInfo">The culture info.</param>
		public DataResourceWriter (Database database, string baseName, CultureInfo cultureInfo)
		{
			if (database == null)
				throw new ArgumentNullException("database");

			this.database = database;

			if (baseName == null)
				this.baseName = String.Empty;
			else
				this.baseName = baseName;

			if (cultureInfo == null)
				this.cultureName = "neutral";
			else
				this.cultureName = (cultureInfo.Name.Length == 0) ? "neutral" : cultureInfo.Name;

			resources = new SortedList();
		}

		/// <summary>
		/// Use C# destructor syntax for finalization code.
		/// </summary>
		/// <remarks>
		/// This destructor will run only if the Dispose method
		/// does not get called.
		/// It gives your base class the opportunity to finalize.
		/// Do not provide destructors in types derived from this class.
		/// </remarks>
		~DataResourceWriter()
		{
			// Do not re-create Dispose clean-up code here.
			// Calling Dispose(false) is optimal in terms of
			// readability and maintainability.
			Dispose(false);
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Adds a named resource of type <see cref="IResourceDataNode"></see> to the list of resources to be written.
		/// </summary>
		/// <param name="resourceDataNode">The resource data node.</param>
		/// <exception cref="T:System.ArgumentNullException">The resourceDataNode parameter is null. </exception>
		public void AddResource(IResourceDataNode resourceDataNode)
		{
			if (resourceDataNode == null)
				throw new ArgumentNullException("resourceDataNode");

			AddResource(resourceDataNode.Name, (object)resourceDataNode);
		}

		/// <summary>
		/// Adds a named resource of type <see cref="T:System.Object"></see> to the list of resources to be written.
		/// </summary>
		/// <param name="name">The name of the resource.</param>
		/// <param name="value">The value of the resource.</param>
		/// <exception cref="T:System.ArgumentNullException">The name parameter is null. </exception>
		public void AddResource(string name, object value)
		{
			if (String.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");

			resources.Add(name, value);
		}

		/// <summary>
		/// Adds an 8-bit unsigned integer array as a named resource to the list of resources to be written.
		/// </summary>
		/// <param name="name">Name of a resource.</param>
		/// <param name="value">Value of a resource as an 8-bit unsigned integer array.</param>
		/// <exception cref="T:System.ArgumentNullException">The name parameter is null. </exception>
		public void AddResource (string name, byte[] value)
		{
			AddResource(name, (object)value);
		}

		/// <summary>
		/// Adds a named resource of type <see cref="T:System.String"></see> to the list of resources to be written.
		/// </summary>
		/// <param name="name">The name of the resource.</param>
		/// <param name="value">The value of the resource.</param>
		/// <exception cref="T:System.ArgumentException">The name parameter is null. </exception>
		public void AddResource (string name, string value)
		{
			AddResource (name, (object)value);
		}

		/// <summary>
		/// Closes the underlying resource file or stream, ensuring all the data has been written to the file.
		/// </summary>
		public void Close ()
		{
			Dispose();
		}

		/// <summary>
		/// Writes all the resources added by the <see cref="M:System.Resources.IResourceWriter.AddResource(System.String,System.String)"></see> method to the output file or stream.
		/// </summary>
		public void Generate ()
		{
			if (resources.Count > 0)
			{
				using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew))
				{
					DbCommand clearCommand = database.GetStoredProcCommand("ClearResource");
					database.AddInParameter(clearCommand, "@BaseName", DbType.String, baseName);
					database.AddInParameter(clearCommand, "@Culture", DbType.String, cultureName);

					database.ExecuteNonQuery(clearCommand);

					foreach (DictionaryEntry resource in resources)
					{
						string typeName;
						byte[] valueBytes;
						object comment;

						ResourceDataNode resourceDataNode = resource.Value as ResourceDataNode;
						if (resourceDataNode == null)
						{
							typeName = resource.Value.GetType().AssemblyQualifiedName;
							valueBytes = SerializationUtility.ToBytes(resource.Value);
							comment = DBNull.Value;
						}
						else
						{
							if (resourceDataNode.FileRef == null)
							{
								typeName = resourceDataNode.TypeName;
								valueBytes = SerializationUtility.ToBytes(resourceDataNode.Value);
							}
							else
							{
								typeName = (resourceDataNode.FileRef).GetType().AssemblyQualifiedName;
								valueBytes = SerializationUtility.ToBytes(resourceDataNode.FileRef);
							}
							comment = resourceDataNode.Comment;
						}

						DbCommand insertCommand = database.GetStoredProcCommand("SetResource");
						database.AddInParameter(insertCommand, "@BaseName", DbType.String, baseName);
						database.AddInParameter(insertCommand, "@Culture", DbType.String, cultureName);
						database.AddInParameter(insertCommand, "@Name", DbType.String, (string)resource.Key);
						database.AddInParameter(insertCommand, "@Type", DbType.String, typeName);
						database.AddInParameter(insertCommand, "@MimeType", DbType.String, DBNull.Value);
						database.AddInParameter(insertCommand, "@value", DbType.Binary, valueBytes);
						database.AddInParameter(insertCommand, "@Comment", DbType.String, comment);

						database.ExecuteNonQuery(insertCommand);
					}
					resources.Clear();
					transactionScope.Complete();
				}
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <remarks>
		/// Implement IDisposable.
		/// Do not make this method virtual.
		/// A derived class should not be able to override this method.
		/// </remarks>
		public void Dispose ()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}
		#endregion

		#region Private Methods
		// Dispose(bool disposing) executes in two distinct scenarios.
		// If disposing equals true, the method has been called directly
		// or indirectly by a user's code. Managed and unmanaged resources
		// can be disposed.
		// If disposing equals false, the method has been called by the
		// runtime from inside the finalizer and you should not reference
		// other objects. Only unmanaged resources can be disposed.
		private void Dispose (bool disposing)
		{
			// Check to see if Dispose has already been called.
			if ( !this.disposed )
			{
				// If disposing equals true, dispose all managed
				// and unmanaged resources.
				if ( disposing )
				{
					// Dispose managed resources.
					if (resources.Count > 0)
						Generate();
				}

				// Note disposing has been done.
				disposed = true;
			}
		}
		#endregion
	}
}
